package logo.lang;

import logo.sys.*;
import java.util.*;

/**
 * @author Ferron Hanse
 * @version 2.0
 */

public class LogoEvaluator implements Visitor {

	
	boolean lock = true;
	
	///// Main Visitors /////

	/** program start point */
	public Object visitProg(IRProgram irProgram, Object info)
			throws LogoException, Exception {
		return irProgram.getSeq().visit(this, info);

	}

	/** the following iterates through the statement list */
	public Object visitSequence(IRCmdSeq seq, Object arg) throws Exception {
		ArrayList sq = seq.getSeq();
		Iterator iter = sq.iterator();
		LogoState res = null;
		while (iter.hasNext()) {
			IRCmd s = (IRCmd) iter.next();
			res = (LogoState) s.visit(this, arg);
		}
		return res;
	}
	
	/** the following method calls a method from LogoDictionary supplying a param */
	public Object visitCall(IRCmdCall irCmdCall, Object info) throws Exception {
		IRExpVar proc = (IRExpVar) irCmdCall.getProc();
		ArrayList lst = irCmdCall.getLst();
		LogoDictionary dictionary = (LogoDictionary)((LogoState)info).getDict();
		
		IRProc val = (IRProc) ((LogoState)info).getDict().get(proc.getVar());
		
		Iterator iter = lst.iterator();
		ArrayList tmplist = new ArrayList ();
		
		while(iter.hasNext()) {
			IRExp tempval = (IRExp) iter.next();
			tmplist.add(tempval.visit(this, info));
		}
		
		((LogoState)info).setDict(dictionary.extend(val.getV(), tmplist));
		
		val.getCmds().visit(this, info);
		((LogoState)info).setDict(dictionary);
		
		return info;
		
		//to square :s repeat 4 [fd :s rt 90] end
		//square 100
	}

	/** adds a procedure to a dictionary */
	public Object visitProc(IRCmdProc irCmdProc, Object info) throws Exception {
		String name = (String) irCmdProc.getName();
		ArrayList ls = irCmdProc.getParams();
		IRCmdSeq temp = (IRCmdSeq) irCmdProc.getSeq();
		((LogoState) info).getDict().put(name, new IRProc(name, ls, temp));
		return info;
	}

	
	///// Main Visitors end /////
	
	
	///// Operation Visitors /////
		
	/** add 2 numbers */
	public Object visitAdd(IRExpAdd irExpAdd, Object info)
			throws LogoException, Exception {

		LogoNum val1, val2;
		val1 = (LogoNum) irExpAdd.getChild1().visit(this, info);
		val2 = (LogoNum) irExpAdd.getChild2().visit(this, info);
		return val1.add(val2);
	}

	/** subtract two values */
	public Object visitSub(IRExpSub irExpSub, Object info)
			throws LogoException, Exception {
		LogoNum val1, val2;
		val1 = (LogoNum) irExpSub.getChild1().visit(this, info);
		val2 = (LogoNum) irExpSub.getChild2().visit(this, info);
		return val1.sub(val2);
	}


	/** Divide 2 numbers */
	public Object visitDiv(IRExpDiv irExpDiv, Object info)
			throws LogoException, Exception {
		LogoNum val1, val2;
		val1 = (LogoNum) irExpDiv.getChild1().visit(this, info);
		val2 = (LogoNum) irExpDiv.getChild2().visit(this, info);
		return val1.div(val2);	
	}

	/** multiply 2 number */
	public Object visitMul(IRExpMul irExpMul, Object info)
			throws LogoException, Exception {
		LogoNum val1, val2;
		val1 = (LogoNum) irExpMul.getChild1().visit(this, info);
		val2 = (LogoNum) irExpMul.getChild2().visit(this, info);
		return val1.mul(val2);
	}
	
	/**return the mod of 2 number */
	public Object visitMod(IRExpMod irExpMod, Object info)
			throws LogoException, Exception {
		LogoNum val1, val2;
		val1 = (LogoNum) irExpMod.getChild1().visit(this, info);
		val2 = (LogoNum) irExpMod.getChild2().visit(this, info);
		return val1.mod(val2);
	}

	/** find the exponential of a value */
	public Object visitExpt(IRExpExpt irExpExpt, Object info)
			throws LogoException, Exception {
		LogoNum val1, val2;
		val1 = (LogoNum) irExpExpt.getChild1().visit(this, info);
		val2 = (LogoNum) irExpExpt.getChild2().visit(this, info);

		return val1.expt(val2);
	}

	//// Operation Visitors end ////
	
	

	//// Equality Operations /////
	
	/** return true if node x == node y */
	public Object visitEq(IRExpEq irExpEq, Object info) throws LogoException,
			Exception {
		LogoNum val1, val2;
		val1 = (LogoNum) irExpEq.getChild1().visit(this, info);
		val2 = (LogoNum) irExpEq.getChild2().visit(this, info);
		return val1.eq(val2);
	}

	/** returns true if node x is < node y */
	public Object visitLt(IRExpLt irExpLt, Object info) throws LogoException,
			Exception {
		LogoNum val1, val2;
		val1 = (LogoNum) irExpLt.getChild1().visit(this, info);
		val2 = (LogoNum) irExpLt.getChild2().visit(this, info);
		return val1.lt(val2);
	}

	/** return true if node x > node y*/
	public Object visitGt(IRExpGt irExpGt, Object info) throws Exception {
		LogoNum val1 = (LogoNum) irExpGt.getChild1().visit(this, info);
		LogoNum val2 = (LogoNum) irExpGt.getChild2().visit(this, info);
		return val1.gt(val2);
	}
	
	///// Equality Operations end /////
	
	
	///// Directional Visitors /////

	/**  
	 * This method was indeed a milestone method! Greatest achievement!
	 * the following method calls the execute the home state
	 * */
	public Object visitHome(IRCmdHome irCmdHome, Object info) {
		((LogoState)info).getTurtle().home();
		return info;

	}
	
	/** move turtle forward n pixels */
	public Object visitForward(IRCmdForward cmd, Object arg) throws Exception {
		LogoState state = (LogoState) arg;
		LogoNum val1 = (LogoNum) cmd.getChild().visit(this, arg);
		state.getTurtle().forward(val1.doubleValue());
		return state;
	}
	
	/** move turtle backward n pixels */
	public Object visitBackward(IRCmdBackward cmd, Object arg) throws Exception {
		LogoNum val = (LogoNum) cmd.getChild().visit(this, arg);
		((LogoState)arg).getTurtle().back(val.doubleValue());
		return arg;
	}

	/** turn turtle left */
	public Object visitLeft(IRCmdLeft irCmdLeft, Object info) throws Exception {
		LogoNum val = (LogoNum) irCmdLeft.getChild().visit(this, info);
		((LogoState)info).getTurtle().left(val.doubleValue());
		return info;
	}
	
	/** turns the turtle right */
	public Object visitRight(IRCmdRight irCmdRight, Object info)
			throws LogoException, Exception {
		LogoNum val = (LogoNum) irCmdRight.getChild().visit(this, info);
		((LogoState)info).getTurtle().right(val.doubleValue());
		return  info;
	}

	//Directional Visitors end
	
	
	//Looping Visitors 
	
	/** run turtle forever */
	public Object visitForever(IRCmdForever irCmdForever, Object info) throws Exception {		
		while(lock) {
			irCmdForever.getBody().visit(this, info);
		}
		return true;
	}

	/** repeats operation (n) times */
	public Object visitRepeat(IRCmdRepeat irCmdRepeat, Object info) throws LogoException, Exception {
		
		LogoNum rep = (LogoNum) irCmdRepeat.getInteger().visit(this, info);
		Object result = null;
		
		for (int i = 0; i <= rep.intValue(); i++) {
			result = irCmdRepeat.getSeq().visit(this, info);
		}
		return result;
	}

	//Looping Visitors end
	
	//Display Visitors
	
	/** clear the canvas area */
	public Object visitClear(IRCmdClear irCmdClear, Object info) {
		LogoState state = (LogoState) info;
		state.getTurtle().clear();
		return state;
	}

	/** hide the turtle */
	public Object visitHide(IRCmdHide irCmdHide, Object info) {
		((LogoState) info).getTurtle().hide();
		return info;
	}

	/** call the the show state */
	public Object visitShow(IRCmdShow irCmdShow, Object info) {
		((LogoState) info).getTurtle().show();
		return info;
	}

	/** place pen on drawing area */
	public Object visitPendown(IRCmdPendown irCmdPendown, Object info) {
		((LogoState)info).getTurtle().penDown();
		return info;
	}

	/** raise pen from drawing area */
	public Object visitPenup(IRCmdPenup irCmdPenup, Object info) {
		((LogoState)info).getTurtle().penUp();
		return info;
	}

	/** set the background color */
	public Object visitSetBackColour(IRCmdSetBackColour irCmdSetBackColour,
			Object info) throws Exception {
		
		LogoVal red, green, blue;
		
		red = (LogoVal) irCmdSetBackColour.getChild1().visit(this, info);
		green = (LogoVal) irCmdSetBackColour.getChild2().visit(this,info);
		blue = (LogoVal) irCmdSetBackColour.getChild3().visit(this,	info);
		((LogoState) info).getTurtle().setBackColour(red, green, blue);
		
		return info;
	}

	/** change pen colour */
	public Object visitSetPenColour(IRCmdSetPenColour irCmdSetPenColour,
			Object info) throws LogoException, Exception {
		LogoVal red = (LogoVal) irCmdSetPenColour.getChild1().visit(this, info);
		LogoVal green = (LogoVal) irCmdSetPenColour.getChild2().visit(this, info);
		LogoVal blue = (LogoVal) irCmdSetPenColour.getChild3().visit(this, info);
		LogoState state = (LogoState) info;
		state.getTurtle().setPenColour(red, green, blue);
		return state;
	}

	/** sets the x y coordinate of the turtle */
	public Object visitSetXY(IRCmdSetXY irCmdSetXY, Object info)
			throws LogoException, Exception {
		LogoNum val1 = (LogoNum) irCmdSetXY.getChild1().visit(this, info);
		LogoNum val2 = (LogoNum) irCmdSetXY.getChild2().visit(this, info);
		LogoState state = (LogoState) info;
		state.getTurtle().setPosition(val1.intValue(), val2.intValue());
		return state;
	}

	/** the following visitor calls the set heading state*/
	public Object visitSeth(IRCmdSeth irCmdSeth, Object info)
			throws LogoException, Exception {
		LogoNum val = (LogoNum) irCmdSeth.getChild().visit(this, info);
		((LogoState) info).getTurtle().setHeading(val.intValue());
		return info;
	}
	
	
	//Display Visitors end

	//Conditional Visitors 
	
	/** perform an if/if else operation before updating state */
	public Object visitIf(IRCmdIf irCmdIf, Object info) throws Exception {
		LogoBool pred = (LogoBool)irCmdIf.getChild1().visit(this, info);
		if (pred.isTrue())
			 irCmdIf.getChild2().visit(this, info);
		else irCmdIf.getChild3().visit(this, info);
		
		return info;
	}
	
	//Conditional Visitors end

	
	// Other 
	
	//Well i was bored so this out
	public Object visitStop(IRCmdStop cmd, Object arg) throws LogoException {
		//TODO Need to fix this
		return null;
	}
	
	/** return a constant */
	public Object visitConst(IRExpConst irExpConst, Object info) throws LogoWrongTypeException {
		return irExpConst.getValue();
	}
	
	/** return the variable from the dictionary */
	public Object visitVar(IRExpVar irExpVar, Object info) throws LogoException {
		//LogoDictionary dict = (LogoDictionary) info;
		return ((LogoState)info).getDict().get(irExpVar.getVar());
	}

	
}// Evaluator
